import random
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericRelation
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models
from django.urls import reverse
from django.utils.timezone import now
from django.utils.text import slugify
from django_ckeditor_5.fields import CKEditor5Field
import logging
from ctbloge.apps.accounts.models import UserProfile
from ctbloge.apps.blog.models import BaseModel
from markdown import Markdown
import html2text
import os
from django.utils.text import get_valid_filename  # 添加文件名过滤
import re
from googletrans import Translator  # 翻译模块
from pypinyin import Style, lazy_pinyin  # 拼音模块


logger = logging.getLogger(__name__)


def generate_unique_slug(instance):
    """生成唯一的ASCII slug（带数字后缀防冲突）"""
    # 转换为拼音首字母并清理特殊字符
    initials = ''.join(lazy_pinyin(instance.title, style=Style.FIRST_LETTER))
    base_slug = re.sub(r'[^a-zA-Z0-9]', '', initials).lower()

    # 获取模型类
    model_class = instance.__class__

    # 生成唯一slug
    counter = 1
    original_slug = base_slug
    while model_class.objects.filter(slug=original_slug).exists():
        original_slug = f"{base_slug}-{counter}"
        counter += 1

    return original_slug


class Category(BaseModel):
    """支持多级的文章分类模型"""
    name = models.CharField('分类名称', max_length=50)
    slug = models.SlugField('URL标识', max_length=60, blank=True,unique=True)
    parent = models.ForeignKey(
        'self',
        verbose_name='父级分类',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='children'
    )
    description = models.TextField('分类描述', blank=True)
    # 封面图
    cover = models.ImageField(
        verbose_name='封面图',
        upload_to='cms/category/%Y-%m-%d/',
        default='default/category/basis_category.png',
        null=True,
        blank=True,
        help_text='上传图片尺寸应为 800px * 400px'
    )

    class Meta:
        verbose_name = '文章分类'
        verbose_name_plural = verbose_name
        ordering = ['rank']
        unique_together = [['name', 'parent']]  # 同一父分类下名称唯一

    def __str__(self):
        if self.parent:
            return f"{self.parent} → {self.name}"
        return self.name

    def save(self, *args, **kwargs):
        # 自动生成唯一slug
        if not self.slug:
            # 创建翻译器实例
            translator = Translator()

            try:
                # 自动检测语言并翻译为英文
                translated = translator.translate(self.name, dest='en').text
                base_slug = slugify(translated)
            except Exception as e:
                # 翻译失败时回退方案：使用拼音或原名称
                pinyin = ''.join(lazy_pinyin(self.name))
                base_slug = slugify(pinyin)

            # 如果有父分类，组合父级slug
            if self.parent:
                base_slug = f"{self.parent.slug}-{base_slug}"

            # 生成唯一slug（排除自身）
            self.slug = base_slug
            counter = 1
            while Category.objects.filter(slug=self.slug).exclude(id=self.id).exists():
                self.slug = f"{base_slug}-{counter}"
                counter += 1

        # 防止循环父级
        if self.parent and self.parent.id == self.id:
            raise ValidationError("不能将自己设为父级分类")

        # 如果修改父分类，自动清除可能失效的rank值
        if self.pk and self.parent_id != Category.objects.get(pk=self.pk).parent_id:
            self.rank = None

        super().save(*args, **kwargs)

    def clean(self):
        # 防止多级循环
        if self.parent:
            parent = self.parent
            while parent is not None:
                if parent == self:
                    raise ValidationError("检测到循环父级关系")
                parent = parent.parent

    def get_absolute_url(self):
        return reverse('category-detail', kwargs={'pk': self.pk})

    @property
    def is_root(self):
        """判断是否为一级分类"""
        return self.parent is None

    @property
    def children(self):
        """获取所有子分类"""
        return self.subcategories.all()

    def get_articles_count(self):
        """获取分类下文章数量"""
        return self.articles.count()


class Tag(BaseModel):
    """文章标签模型"""
    name = models.CharField('标签名称', max_length=30, unique=True)
    color_code = models.CharField('颜色代码', max_length=7, blank=True)
    description = models.TextField('标签描述', blank=True)

    class Meta:
        verbose_name = '文章标签'
        verbose_name_plural = verbose_name
        ordering = ['-created_date']

    def __str__(self):
        return self.name

    def save(self, *args, **kwargs):
        if not self.color_code:
            # 生成随机的十六进制颜色代码
            self.color_code = "#{:06x}".format(random.randint(0, 0xFFFFFF))
        #如果没有标签描述，则自动生成

        super().save(*args, **kwargs)

    def get_absolute_url(self):
        return reverse('tag-detail', kwargs={'pk': self.pk})


class Topic(BaseModel):
    """文章专题模型"""
    name = models.CharField('专题名称', max_length=50, unique=True)
    slug = models.SlugField('URL标识', max_length=60)
    description = models.TextField('专题描述', blank=True)

    class Meta:
        verbose_name = '专题列表'
        verbose_name_plural = verbose_name
        ordering = ['-created_date']

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse('topic-detail', kwargs={'pk': self.pk})


class Like(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, verbose_name='点赞用户')
    created_date = models.DateTimeField(auto_now_add=True, verbose_name='点赞时间')
    # 通用外键支持任何模型
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, verbose_name='点赞类型')
    object_id = models.PositiveIntegerField('点赞对象ID')
    content_object = GenericForeignKey('content_type', 'object_id')

    class Meta:
        unique_together = ('user', 'content_type', 'object_id')


class Collection(models.Model):
    """用户收藏记录"""
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        verbose_name='收藏用户'
    )
    content_type = models.ForeignKey(
        ContentType,
        on_delete=models.CASCADE,
        verbose_name='收藏类型'
    )
    object_id = models.PositiveIntegerField('收藏对象ID')
    content_object = GenericForeignKey()
    created_date = models.DateTimeField(
        '收藏时间',
        auto_now_add=True
    )

    class Meta:
        verbose_name = '用户收藏'
        verbose_name_plural = verbose_name
        unique_together = ('user', 'content_type', 'object_id')  # 防止重复收藏

    def __str__(self):
        return f"{self.user} 收藏 #{self.id}"


class Article(BaseModel):
    """文章核心模型"""
    STATUS_CHOICES = (
        ('draft', '草稿'),
        ('pending', '待审核'),
        ('published', '已发布'),
        ('rejected', '已驳回'),
        ('deleted', '已删除'),

    )
    ARTICLE_TYPES = (
        ('no_image', '无图'),
        ('single', '单图'),
        ('multi', '多图'),
    )
    title = models.CharField('标题', max_length=200)
    slug = models.SlugField('URL标识', max_length=220, unique_for_date='publish_date', blank=True)
    content = CKEditor5Field(verbose_name='内容',config_name='extends')
    excerpt = models.TextField('摘要', blank=True)
    author = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        related_name='articles',
        verbose_name='作者'
    )
    category = models.ForeignKey(
        Category,
        on_delete=models.PROTECT,
        related_name='articles',
        verbose_name='所属分类'
    )
    tags = models.ManyToManyField(
        Tag,
        related_name='articles',
        blank=True,
        verbose_name='文章标签'
    )
    topics = models.ForeignKey(
        Topic,
        on_delete=models.PROTECT,
        related_name='articles',
        verbose_name='所属专题',
        null=True,
        blank=True
    )
    article_type = models.CharField(
        verbose_name='文章类型',
        max_length=20,
        choices=ARTICLE_TYPES,
        default='no_image'
    )
    image = models.ImageField(
        verbose_name='文章图片',
        upload_to='cms/articles/%Y-%m-%d/',
        null=True,
        blank=True,
        help_text='上传图片尺寸应为 800px * 400px'
    )
    status = models.CharField(
        verbose_name='文章状态',
        max_length=10,
        choices=STATUS_CHOICES,
        default='draft'
    )
    publish_date = models.DateTimeField('发布时间', null=True, blank=True)

    is_commentable = models.BooleanField(
        verbose_name='是否开放评论',
        default='True'
    )
    is_top = models.BooleanField(
        verbose_name='是否置顶',
        default=False,
        help_text='置顶文章会优先展示'
    )
    is_recommended = models.BooleanField(
        verbose_name='是否推荐',
        default=False,
        help_text='推荐文章会被推荐到首页'
    )
    likes = GenericRelation(Like, related_query_name='articles', verbose_name='点赞记录')
    like_count = models.PositiveIntegerField(
        verbose_name='点赞数',
        default=0,
        editable=False  # 防止后台直接修改
    )
    # 在Article类的字段区域添加
    collected = GenericRelation(
        Collection,
        related_query_name='articles',
        verbose_name='收藏记录'
    )
    collect_count = models.PositiveIntegerField(
        verbose_name='收藏量',
        default=0,
        editable=False
    )
    views_count = models.PositiveIntegerField(
        verbose_name='浏览量',
        default=0,
        editable=False  # 防止后台直接修改
    )
    comments_count = models.PositiveIntegerField(
        verbose_name='评论数',
        default=0,
        editable=False  # 防止后台直接修改
    )

    class Meta:
        verbose_name = '文章列表'
        verbose_name_plural = verbose_name
        ordering = ['-is_top', '-publish_date']  # 置顶文章优先
        permissions = [
            ('can_publish', "可以发布文章"),
            ('can_review', "可以审核文章"),
            ('can_reject', "可以驳回文章"),
        ]
        indexes = [
            models.Index(fields=['-publish_date']),
            models.Index(fields=['status']),
            models.Index(fields=['is_top']),  # 新增置顶索引
            models.Index(fields=['is_recommended']),  # 新增推荐索引
            models.Index(fields=['article_type']),  # 类型索引
            models.Index(fields=['like_count']),  # 点赞数索引
            models.Index(fields=['views_count']),  # 浏览量索引
            models.Index(fields=['collect_count']),  # 收藏量索引
            models.Index(fields=['comments_count']),  # 评论数索引
        ]

    def __str__(self):
        return self.title
    #阅读量统计
    def viewed(self):
        self.views_count += 1
        self.save(update_fields=['views_count'])
    def get_md(self):
        """渲染 Markdown 内容"""
        markdown_content = html2text.html2text(self.content)  # 先将 HTML 转换为 Markdown 格式

        # 创建 Markdown 实例并生成目录
        md = Markdown(
            extensions=[
                'markdown.extensions.extra',
                'markdown.extensions.toc',
                'markdown.extensions.codehilite',
            ]
        )
        md_body = md.convert(markdown_content)

        return md_body, md.toc

    def save(self, *args, **kwargs):
        if not self.excerpt:
            self.excerpt = self.content[:150] + '。。。'  # 自动生成摘要

        if not self.slug:
            self.slug = generate_unique_slug(self)  # 自动生成URL标识

        if not self.publish_date:
            if self.status == 'published':  # 如果没有填写发布时间，且状态为已发布，则自动设置为当前时间
                self.publish_date = now()

        # 导出 Markdown 内容为文件
        if self.status == 'pending':
            try:
                # 1. 使用规范的YAML格式
                frontmatter = f"""---
title: "{self.title}"
slug: "{self.slug}"
author:
  - name: "{self.author.username if self.author else '未知'}"
  - email: "{self.author.email if self.author else ''}"
article_type: "{self.article_type}"
image: "{self.image.url if self.image else ''}"
images: [{', '.join(f'"{img.image.url}"' for img in self.images.all())}]
publish_date: "{self.publish_date.strftime('%Y-%m-%d %H:%M:%S') if self.publish_date else ''}"
category:
  - name: "{self.category.name}"
  - slug: "{self.category.slug}"
  - description: "{self.category.description}"
topics: 
  - name: "{self.topics.name if self.topics else ''}"
  - description: "{self.topics.description if self.topics else ''}"
tags: [{', '.join(f'"{tag.name}"' for tag in self.tags.all())}]
---"""

                # 2. 使用现有的get_md方法避免重复转换
                md_body, _ = self.get_md()

                # 3. 优化文件路径生成
                safe_title = get_valid_filename(self.title)[:50]  # 限制文件名长度
                file_name = f"{self.id}_{safe_title}.md"
                file_path = os.path.join(
                    settings.MEDIA_ROOT,
                    'cms/md',
                    file_name
                )

                # 4. 原子化写入操作
                os.makedirs(os.path.dirname(file_path), exist_ok=True)
                with open(file_path, 'w', encoding='utf-8') as f:
                    f.write(frontmatter)
                    f.write("\n")  # 确保元数据与内容分隔
                    f.write(md_body)
                    # 5. 记录生成日志
                    logger.info(f"Markdown文件已生成：{file_path}")

                # 6. 清理旧文件（可选）
                if not self._state.adding:  # 更新操作时清理旧文件
                    pattern = re.compile(rf"^{self.id}_\d+_{safe_title}\.md$")
                    for old_file in os.listdir(os.path.dirname(file_path)):
                        if pattern.match(old_file) and old_file != file_name:
                            os.remove(os.path.join(os.path.dirname(file_path), old_file))

            except IOError as e:
                logger.error(f"文件写入失败：{str(e)}")
                raise ValidationError("无法生成Markdown文件，请检查存储权限")
            except Exception as e:
                logger.error(f"生成Markdown时发生意外错误：{str(e)}")
                raise
        if self.status == 'pending' and self._state.adding is False:  # 新增文章或修改已发布文章时，通知管理员审核
            original = Article.objects.get(pk=self.pk)
            if original.status == 'pending':
                # 发布文章后，通知管理员审核
                from ctbloge.apps.comments.models import Notification
                Notification.objects.create(
                    sender=self.author,
                    verb='message',
                    receiver=UserProfile.objects.filter(is_superuser=True).first(),
                    content=f"{self.title} 文章已发布，请及时审核！"
                )
        super().save(*args, **kwargs)



    def get_absolute_url(self):  # 重写get_absolute_url方法，返回文章详情页的url
        return reverse('article-detail', kwargs={'pk': self.pk})


class ArticleImage(models.Model):
    article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='images')
    image = models.ImageField(upload_to='cms/articles/%Y-%m-%d/')
    order = models.PositiveIntegerField(default=0)
    is_primary = models.BooleanField('是否为主图', default=False)
